// exception.cc 
//    Entry point into the Nachos kernel from user programs.
//    There are two kinds of things that can cause control to
//    transfer back to here from user code:
//
//    syscall -- The user code explicitly requests to call a procedure
//    in the Nachos kernel.  Right now, the only function we support is
//    "Halt".
//
//    exceptions -- The user code does something that the CPU can't handle.
//    For instance, accessing memory that doesn't exist, arithmetic errors,
//    etc.  
//
//    Interrupts (which can also cause control to transfer from user
//    code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include <stdio.h>        // FA98
#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "addrspace.h"   // FA98
#include "sysdep.h"   // FA98

// begin FA98

static int SRead(int addr, int size, int id);
static void SWrite(char *buffer, int size, int id);


// end FA98


static void ProcessCreator(int nullArg);
static void arraySizeChange();
static int tCount = -1;
//----------------------------------------------------------------------
// ExceptionHandler
//     Entry point into the Nachos kernel.  Called when a user program
//    is executing, and either does a syscall, or generates an addressing
//    or arithmetic exception.
//
//     For system calls, the following is the calling convention:
//
//     system call code -- r2
//        arg1 -- r4
//        arg2 -- r5
//        arg3 -- r6
//        arg4 -- r7
//
//    The result of the system call, if any, must be put back into r2. 
//
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//    "which" is the kind of exception.  The list of possible exceptions 
//    are in machine.h.
//----------------------------------------------------------------------
int threadCount = 0;
void
ExceptionHandler(ExceptionType which)
{
    int type = machine->ReadRegister(2);

    int arg1 = machine->ReadRegister(4);
    int arg2 = machine->ReadRegister(5);
    int arg3 = machine->ReadRegister(6);
    int Result;
    int i, j;
    char *ch = new char [500];
    //Thread *t;
    IntStatus intState = IntOff;
    char *fileName = new char[100];
    int prcsID = 0;
    
    printf("\nExceptionHandler entered.\n");
    
   // machine->DumpState();
    
    switch ( which )
    {
    case NoException :
        break;
    case SyscallException :

        // for debugging, in case we are jumping into lala-land
        // Advance program counters.
        machine->registers[PrevPCReg] = machine->registers[PCReg];
        machine->registers[PCReg] = machine->registers[NextPCReg];
        machine->registers[NextPCReg] = machine->registers[NextPCReg] + 4;

        switch ( type )
        {

        case SC_Halt :
            printf ("\nBeginning SC_Halt.\n");
            DEBUG('t', "Shutdown, initiated by user program.\n");
            interrupt->Halt();
            break;

            
        case SC_Read :
            printf ("Beginning SC_Read.\n");
            if (arg2 <= 0 || arg3 < 0){
                printf("\nRead 0 byte.\n");
            }
            Result = SRead(arg1, arg2, arg3);
            machine->WriteRegister(2, Result);
            DEBUG('t',"Read %d bytes from the open file(OpenFileId is %d)",
            arg2, arg3);
            printf ("SC_Read complete.\n");
            break;

        case SC_Write :
            printf ("Beginning SC_Write.\n");
            for (j = 0; ; j++) {
                if(!machine->ReadMem((arg1+j), 1, &i))
                    j=j-1;
                else{
                    ch[j] = (char) i;
                    if (ch[j] == '\0') 
                        break;
                }
            }
            if (j == 0){
                printf("\nWrite 0 byte.\n");
                // SExit(1);
            } else {
                DEBUG('t', "\nWrite %d bytes from %s to the open file(OpenFileId is %d).", arg2, ch, arg3);
                SWrite(ch, j, arg3);
            }
            printf ("SC_Write complete.\n");
            break;

        /* SC_Exec 
                
        This system call takes the filename of a process to be run
        as the argument.
        
        It performs the following actions:
        
        1. Allocation of address space
        2. Checking to see if there is space to run the process
        3. Creation of a new thread/assignment of address space
        4. Assignment of Process ID # for later tracking
        5. Initialization of thread's local state and registers
        6. Adding thread w/ new process to ready queue
        
        Interrupts are disabled when context switches are performed.
        
        -Eric Killen(ekk5147) */    
            
        case SC_Exec :
        {
            printf ("\nBeginning SC_Exec.\n");
            //init filename string
            memset(fileName, 0, 100);
            
            //populate filename string(using shard of SC_Write code)
            for (j = 0; ; j++) {
                if(!machine->ReadMem((arg1+j), 1, &i))
                    j=j-1;
                else{
                    fileName[j] = (char) i;
                    if (fileName[j] == '\0') 
                        break;
                }
            }
            if (j == 0)
                break;
            else 
                printf ("filename '%s' recorded.\n", fileName);
            
            prcsID = Exec(fileName);
			
			machine->WriteRegister(2, prcsID);
            //printf ("ProcessID #%d written to memory.\n", prcsID);
			
            interrupt->Enable();        
            currentThread->Yield(); //allow new process to initialize
            intState = interrupt->SetLevel(IntOff);
            
            
            
            //printf ("Incrementing PC registers.");
            /*machine->registers[PrevPCReg] = machine->registers[PCReg];
            printf (".");
            machine->registers[PCReg] = machine->registers[NextPCReg];
            printf (".");
            machine->registers[NextPCReg] = machine->registers[NextPCReg] + 4;
            printf (".\n"); */
            
            // for loop used to check if the id of the thread array is 
			// equal to the currentThread that is exiting
            /*for(int aa = 0; aa<sizeArray ; aa++)
			{
				if (threadArray[aa]->Id() == currentThread->Id())
				{
					threadArray[aa]->SetId(-1);
				}
			}; */
			
            printf ("SC_Exec complete.\n");
			
            //machine->DumpState();
        }    
            break;
        
        case SC_Exit:
            printf ("\nBeginning SC_Exit.\n");
            SExit(arg1);
            break;
        case SC_Yield:
            printf ("\nBeginning SC_Yield.\n");
            Yield();
            break;
            
        default :
            //Unprogrammed system calls end up here
            break;
        }         
        break;

    case ReadOnlyException :
        puts ("ReadOnlyException");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        //SExit(1);
        break;
    case BusErrorException :
        puts ("BusErrorException");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        interrupt->Halt();//SExit(1);
        break;
    case AddressErrorException :
        puts ("AddressErrorException");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        interrupt->Halt();//SExit(1);
        break;
    case OverflowException :
        puts ("OverflowException");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        //SExit(1);
        break;
    case IllegalInstrException :
        puts ("IllegalInstrException");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        //SExit(1);
        break;
    case NumExceptionTypes :
        puts ("NumExceptionTypes");
        if (currentThread->getName() == "main")
        ASSERT(FALSE);  //Not the way of handling an exception.
        //SExit(1);
        break;

        default :
        //      printf("Unexpected user mode exception %d %d\n", which, type);
        //      if (currentThread->getName() == "main")
        //      ASSERT(FALSE);
        //      SExit(1);
        break;
    }
    delete [] ch;
    delete [] fileName;
}


static int SRead(int addr, int size, int id)  //input 0  output 1
{
    char buffer[size+10];
    int num,Result;

    //read from keyboard, try writing your own code using console class.
    if (id == 0)
    {
        scanf("%s",buffer);

        num=strlen(buffer);
        if(num>(size+1)) {

            buffer[size+1] = '\0';
            Result = size+1;
        }
        else {
            buffer[num+1]='\0';
            Result = num + 1;
        }

        for (num=0; num<Result; num++)
        {  machine->WriteMem((addr+num), 1, (int) buffer[num]);
            if (buffer[num] == '\0')
            break; }
        return num;

    }
    //read from a unix file, later you need change to nachos file system.
    else
    {
        for(num=0;num<size;num++){
            Read(id,&buffer[num],1);
            machine->WriteMem((addr+num), 1, (int) buffer[num]);
            if(buffer[num]=='\0') break;
        }
        return num;
    }
}



static void SWrite(char *buffer, int size, int id)
{
    //write to terminal, try writting your own code using console class.
    if (id == 1)
    printf("%s", buffer);
    //write to a unix file, later you need change to nachos file system.
    if (id >= 2)
    WriteFile(id,buffer,size);
}

// end FA98

SpaceId Exec(char *name)
{
    OpenFile *executable;
    AddrSpace *space;
    IntStatus intState = IntOff;
	Thread *t;
    //int processID = 0;
	printf("test0\n");
	//char *thName = new char[30];
	char *tNameID = new char[1];
	//char *tName = {"userThread   %%"};
    
	tCount++;
	//printf("test1\n");
	tNameID[0] = '0' + tCount;
	//printf("test2\n");
	//tName[12] = tNameID[0];
	//printf("test3\n");
	t = new Thread(tNameID);
	t->SetId(tCount);
	
	//executable = new OpenFile(0);
	//printf("thread %s created(?)\n", tNameID);
    //use filename string to open actual file and init address space
    executable = fileSystem->Open(name);
  //  printf ("'executable' allocated.\n");
    if (!executable)
        printf ("Warning: 'executable' is null.\n");
    space = new AddrSpace(executable);
   // printf ("'space' allocated.\n");
    delete executable;        
    //*TODO* check if there is space to run new process *TODO*
            
    // check threadArray to see if it is at max size
	if(sizeCount == sizeArray)
	{
		arraySizeChange();
	};
	/* create new thread and give it the allocated address space
    Thread *t = new Thread("userThread");
	// put copy of the thread in the thread Array
	*/printf("Size count is %d \n", sizeCount);
	Processes[sizeCount].active = t;
	Processes[sizeCount].pName = t->getName();
	printf("Process Array: ");
	for (int i=0; i<sizeArray; i++)
	{
		if (Processes[i].active != NULL) {
			printf("%s " ,Processes[i].pName);
		}
	}
	printf("\n");
	printf("*************** \n"); 
	sizeCount = sizeCount + 1;
	
	//printf(" threadArray[sizeCount] %d",sizeCount);
	
   // printf ("new thread '%s' allocated.\n", tNameID);
    t->space = space;
   // printf ("'space' allocated to '%s'.\n", tNameID);            
            
    //initialize registers/state and start new process
    t->Fork(ProcessCreator, 0);
    printf ("'t' forked to ProcessCreator.\n");
    interrupt->Enable();        
    currentThread->Yield(); //allow new process to initialize
    intState = interrupt->SetLevel(IntOff);
            
    //printf ("Yield successful.\n");
    
                    
    //clean up
    
    //delete space;
    
    return(t->space->GetSpaceId());
} 
/*
int Join(SpaceId id)
{
    IntStatus intState = IntOff;
    //int ValidLoc = -1;
    //make currentThread the parent of the thread using SpaceID
    //for(int i = 0; i < ThreadArray.size; i++)
    //{
    //    if (id == ThreadArray[i]->space->spaceID)
    //    {
    //        ValidLoc = i;
    //        break;
    //    }
    //}
    //if (ValidLoc != -1)
    //{    
        //ThreadArray[ValidLoc]->setParent(currentThread);    
        interrupt->Enable();
        currentThread->Sleep();
        intState = interrupt->SetLevel(IntOff);
        return 0;
    //}
    //else {return -1;}
    
} */

void SExit(int status)
{
    int childNum = 0;
    IntStatus intState = IntOff;
    if ((currentThread->GetParent()) && (currentThread->GetParent()->GetStatus() == BLOCKED))
        scheduler->ReadyToRun(currentThread->GetParent());
    /*if (currentThread->Children() > 0)
    //have all of currentThread's children call Exit(1)
    {
        childNum = currentThread->Children();
        for (int i = 0; i < childNum; i++) 
            currentThread->Find(i)->Fork(Exit, 1);
    }    */ 
	
    if (status == 1)
        printf("Process exited abnormally!\n");
    else
        printf("Process exited gracefully.\n");
	
	currentThread->space->DeAlloc();
    interrupt->Enable();
	//scheduler->Print();
    currentThread->Finish();
	//interrupt->Halt();
    intState = interrupt->SetLevel(IntOff); 
	
}  

void Yield()
{
	
    IntStatus intState = IntOff;
    interrupt->Enable();
    //currentThread->SaveUserState();
    currentThread->Yield();
    intState = interrupt->SetLevel(IntOff);
}


static void ProcessCreator(int nullArg)
{
    IntStatus intState = IntOff;
    interrupt->Enable();
    currentThread->Yield();
	
    intState = interrupt->SetLevel(IntOff);
	sizeCount+=1;
	threadCount+=1;
	Processes[sizeCount].active = currentThread;
    //printf("restoring state\n");
    currentThread->space->RestoreState();
   // printf("initializing registers\n");
    currentThread->space->InitRegisters();
    
   // printf("running MIPS simulator\n");
	//scheduler->Print();
	machine->Run();
      ASSERT(FALSE);
}

static void arraySizeChange()
{

	//Thread **temp = new Thread*[sizeArray];
	for(int i = 0; i<sizeArray ; i++)
	{
		//temp[i] = threadArray[i];
	}
	sizeArray = sizeArray + 10;
	delete Processes;
	//threadArray = new Thread*[sizeArray];
	for(int i = 0; i< sizeArray ; i++)
	{
		//threadArray[i] = temp[i];
	}
	//delete temp;
	
}
